package com.gulimall.product.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.gulimall.common.utils.PageUtils;
import com.gulimall.common.utils.Query;
import com.gulimall.product.dao.CategoryDao;
import com.gulimall.product.entity.CategoryEntity;
import com.gulimall.product.service.CategoryService;
import com.gulimall.product.vo.CatalogV2;
import org.jetbrains.annotations.NotNull;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;


@Service("categoryService")
public class CategoryServiceImpl extends ServiceImpl<CategoryDao, CategoryEntity> implements CategoryService {
    @Autowired
    private StringRedisTemplate redisTemplate;
    @Autowired
    private RedissonClient redissonClient;

    @Override
    public PageUtils queryPage(Map<String, Object> params) {
        IPage<CategoryEntity> page = this.page(new Query<CategoryEntity>().getPage(params),
                new QueryWrapper<CategoryEntity>());

        return new PageUtils(page);
    }

    @Override
    public List<CategoryEntity> queryCategoryListTree() {
        List<CategoryEntity> categoryEntities = baseMapper.selectList(null);
        return dfsQueryListTree(categoryEntities, 0L);
    }

    @Override
    public void removeMenusByIds(List<Long> catIds) {
        //TODO 检查当前删除的菜单，是否在其他地方引用
        baseMapper.deleteBatchIds(catIds);
    }

    private List<CategoryEntity> dfsQueryListTree(List<CategoryEntity> allList, Long currId) {
        List<CategoryEntity> resultCategoryList = new ArrayList<>();
        allList.forEach(item -> {
            if (Objects.equals(item.getParentCid(), currId)) {
                resultCategoryList.add(item);
                item.setChildren(dfsQueryListTree(allList, item.getCatId()));
            }
        });
        if (resultCategoryList.size() == 0) {
            return null;
        }
        Collections.sort(resultCategoryList,
                (a, b) -> (a.getSort() == null ? 0 : a.getSort()) - (b.getSort() == null ? 0 : b.getSort()));
        return resultCategoryList;
    }

    @Override
    public long[] queryCatelogPath(Long categoryId) {
        List<Long> lists = new ArrayList<>();
        getCatelogParentId(categoryId, lists);
        Collections.reverse(lists);
        long[] res = new long[lists.size()];
        for (int i = 0; i < res.length; i++) {
            res[i] = lists.get(i);
        }
        return res;
    }

    @Override
//    @Cacheable(value = {"category"}, key = "'categoryParentLevel'")
    @Cacheable(value = {"category"}, key = "#root.method.name", sync = true)
    public List<CategoryEntity> queryCategoryParentLevel() {
        System.out.println("queryCategoryParentLevel ... ");
        return list(new QueryWrapper<CategoryEntity>().eq("cat_level", 1));
    }

    @Override
    @Cacheable(value = {"category"}, key = "'getCategoryFromDB'")
    public Map<String, List<CatalogV2>> getCatelogV2List() {
        System.out.println("查询数据库");
        Map<String, List<CatalogV2>> listMap = new HashMap<>();
        List<CategoryEntity> categoryEntities = queryCategoryParentLevel();
        categoryEntities.forEach(l1 -> {
            List<CatalogV2> catelogV2s =
                    list(new QueryWrapper<CategoryEntity>().eq("parent_cid", l1.getCatId())).stream().map(l2 -> {
                        //查询二级节点 构建对象数组
                        CatalogV2 catelogV2 = new CatalogV2(l1.getCatId().toString(), l2.getCatId().toString(),
                                l2.getName(),
                                null);
                        //查找子三级节点
                        List<CatalogV2.Catalog3> catelog3s = list(new QueryWrapper<CategoryEntity>().eq("parent_cid",
                                l2.getCatId())).stream().map(l3 -> new CatalogV2.Catalog3(l2.getCatId().toString(),
                                l3.getCatId().toString(), l3.getName())).collect(Collectors.toList());

                        catelogV2.setCatalog3List(catelog3s);
                        return catelogV2;
                    }).collect(Collectors.toList());

            //获得当前一级节点下的所有分类，并且分装到map中
            listMap.put(l1.getCatId().toString(), catelogV2s);
        });
        return listMap;
    }

    //    @Override
    public Map<String, List<CatalogV2>> getCatelogV2List2() {
        /**
         * 空结果缓存 ：解决缓存穿透
         * 设置过期时间(加随机值) ： 解决缓存雪崩
         * 加锁：解决缓存击穿
         */

        ValueOperations<String, String> forValue = redisTemplate.opsForValue();
        String catalogJSON = forValue.get("catalogJSON");
        if (!StringUtils.hasText(catalogJSON)) {
            return getCatalogJsonFromDBWidthRedissonLock();
        }
        return JSON.parseObject(catalogJSON, new TypeReference<Map<String, List<CatalogV2>>>() {
        });
    }

    /**
     * 使用redisson的分布式锁
     *
     * @return
     */
    private Map<String, List<CatalogV2>> getCatalogJsonFromDBWidthRedissonLock() {
        RLock rLock = redissonClient.getLock("catalogJson-lock");
        rLock.lock();

        Map<String, List<CatalogV2>> catalogList = null;
        try {
            catalogList = getDataFromDB();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            rLock.unlock();
        }
        return catalogList;
    }

    /**
     * 分布式锁
     *
     * @return
     */
    private Map<String, List<CatalogV2>> getCatalogJsonFromDBWidthRedisLock() {
        ValueOperations<String, String> opsForValue = redisTemplate.opsForValue();
        //1.设置分布式锁，每个线程去抢占锁
//        Boolean ifAbsent = opsForValue.setIfAbsent("lock", "123");
        //3.在插入值的时候设置过期时间
        String uuid = UUID.randomUUID().toString();
        Boolean ifAbsent = opsForValue.setIfAbsent("lock", uuid, 300, TimeUnit.SECONDS);

        if (ifAbsent) {
            //抢占锁后，操作数据库
            //2.设置过期时间
//            redisTemplate.expire("lock", 30, TimeUnit.SECONDS);
            Map<String, List<CatalogV2>> catalogList;
            try {
                catalogList = getDataFromDB();
            } finally {
                //4.删除锁前，从redis中获取value值，判断当前线程的锁是否过期、被其他线程重新占领
//            String lock = opsForValue.get("lock");
//            if (uuid.equals(lock)) {
//                //完成操作后，删除锁
//                redisTemplate.delete("lock");
//            }

                Long lock =
                        redisTemplate.execute(new DefaultRedisScript<Long>("if redis.call(\"get\",KEYS[1]) == " +
                                "ARGV[1]" + " then " + "return  redis.call(\"del\",KEYS[1]) else  return 0 " + "end",
                                Long.class), Arrays.asList("lock"), uuid);
            }
            return catalogList;
        } else {
            System.out.println("抢占锁失败，准备等待重新抢锁");
            try {
                Thread.sleep(300);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            return getCatalogJsonFromDBWidthRedisLock();
        }
    }

    /**
     * 本地锁
     *
     * @return
     */
    private Map<String, List<CatalogV2>> getCatalogJsonFromDBWidthLocalLock() {
        synchronized (this) {
            return getDataFromDB();
        }
    }


    public Map<String, List<CatalogV2>> getDataFromDB() {
        ValueOperations<String, String> forValue = redisTemplate.opsForValue();
        String catalogJSON = forValue.get("catalogJSON");
        if (StringUtils.hasText(catalogJSON)) {
            return JSON.parseObject(catalogJSON, new TypeReference<Map<String, List<CatalogV2>>>() {
            });
        }
        System.out.println("查询数据库");
        Map<String, List<CatalogV2>> listMap = new HashMap<>();
        List<CategoryEntity> categoryEntities = queryCategoryParentLevel();
        categoryEntities.forEach(l1 -> {
            List<CatalogV2> catelogV2s =
                    list(new QueryWrapper<CategoryEntity>().eq("parent_cid", l1.getCatId())).stream().map(l2 -> {
                        //查询二级节点 构建对象数组
                        CatalogV2 catelogV2 = new CatalogV2(l1.getCatId().toString(), l2.getCatId().toString(),
                                l2.getName(),
                                null);
                        //查找子三级节点
                        List<CatalogV2.Catalog3> catelog3s = list(new QueryWrapper<CategoryEntity>().eq("parent_cid",
                                l2.getParentCid())).stream().map(l3 -> new CatalogV2.Catalog3(l2.getCatId().toString(),
                                l3.getCatId().toString(), l3.getName())).collect(Collectors.toList());

                        catelogV2.setCatalog3List(catelog3s);
                        return catelogV2;
                    }).collect(Collectors.toList());

            //获得当前一级节点下的所有分类，并且分装到map中
            listMap.put(l1.getCatId().toString(), catelogV2s);
        });
        String jsonString = JSON.toJSONString(listMap);
        forValue.set("catalogJSON", jsonString, 1, TimeUnit.DAYS);
        return listMap;
    }

    @Override
//    @Caching(evict = {@CacheEvict(value = {"category"}, key = "'getCategoryFromDB'"), @CacheEvict(value =
//            {"category"}, key = "'queryCategoryParentLevel'")})
    @CacheEvict(value = "category", allEntries = true)
    public void updateCategory(CategoryEntity category) {
        updateById(category);
    }

    private void getCatelogParentId(long attrGroupId, @NotNull List<Long> lists) {
        lists.add(attrGroupId);
        CategoryEntity entity = this.getById(attrGroupId);
        if (entity.getParentCid() != 0) {
            getCatelogParentId(entity.getParentCid(), lists);
        }
    }
}
